﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using Lucene.Linq.Expressions;
using Lucene.Net.Analysis.Standard;

namespace Lucene.Linq.Mapping
{
    internal class FieldProjector : LuceneExpressionVisitor
    {
        private readonly Nominator _nominator;
        private HashSet<Expression> _candidates;
        private string _existingAlias;
        private HashSet<string> _fieldNames;
        private int _fieldNumber;
        private List<FieldDeclaration> _fields;
        private Dictionary<FieldExpression, FieldExpression> _map;
        private string _newAlias;

        internal FieldProjector(Func<Expression, bool> fnCanBeField)
        {
            _nominator = new Nominator(fnCanBeField);
        }

        internal ProjectedFields ProjectFields(Expression expression, string newAlias, string existingAlias)
        {
            _map = new Dictionary<FieldExpression, FieldExpression>();
            _fields = new List<FieldDeclaration>();
            _fieldNames = new HashSet<string>();
            _newAlias = newAlias;
            _existingAlias = existingAlias;
            _candidates = _nominator.Nominate(expression);
            return new ProjectedFields(Visit(expression), _fields.AsReadOnly());
        }

        protected override Expression Visit(Expression expression)
        {
            if (_candidates.Contains(expression))
            {
                string fieldName;
                int ordinal;
                if (expression.NodeType == (ExpressionType) LuceneExpressionType.Field)
                {
                    var field = (FieldExpression) expression;
                    FieldExpression mapped;

                    if (_map.TryGetValue(field, out mapped))
                    {
                        return mapped;
                    }

                    if (_existingAlias == field.Alias)
                    {
                        ordinal = _fields.Count;
                        fieldName = GetUniqueFieldName(field.Name);
                        _fields.Add(new FieldDeclaration(fieldName, field));
                        mapped = new FieldExpression(field.Type, _newAlias, fieldName, ordinal, field.Analyzer);
                        _map[field] = mapped;
                        _fieldNames.Add(fieldName);
                        return mapped;
                    }

                    // must be referring to outer scope
                    return field;
                }
                fieldName = GetNextFieldName();
                ordinal = _fields.Count;
                _fields.Add(new FieldDeclaration(fieldName, expression));
                return new FieldExpression(expression.Type, _newAlias, fieldName, ordinal, new StandardAnalyzer());
            }
            return base.Visit(expression);
        }

        private bool IsFieldNameInUse(string name)
        {
            return _fieldNames.Contains(name);
        }

        private string GetUniqueFieldName(string name)
        {
            string baseName = name;
            int suffix = 1;

            while (IsFieldNameInUse(name))
            {
                name = baseName + (suffix++);
            }

            return name;
        }

        private string GetNextFieldName()
        {
            return GetUniqueFieldName("c" + (_fieldNumber++));
        }

        #region Nested type: Nominator

        private class Nominator : LuceneExpressionVisitor
        {
            private readonly Func<Expression, bool> _canBeFieldFunc;
            private HashSet<Expression> _candidates;
            private bool _isBlocked;

            internal Nominator(Func<Expression, bool> canBeFieldFunc)
            {
                _canBeFieldFunc = canBeFieldFunc;
            }


            internal HashSet<Expression> Nominate(Expression expression)
            {
                _candidates = new HashSet<Expression>();
                _isBlocked = false;
                Visit(expression);
                return _candidates;
            }

            protected override Expression Visit(Expression expression)
            {
                if (expression != null)
                {
                    bool saveIsBlocked = _isBlocked;
                    _isBlocked = false;

                    base.Visit(expression);
                    if (!_isBlocked)
                    {
                        if (_canBeFieldFunc(expression))
                        {
                            _candidates.Add(expression);
                        }
                        else
                        {
                            _isBlocked = true;
                        }
                    }
                    _isBlocked |= saveIsBlocked;
                }
                return expression;
            }
        }

        #endregion
    }
}